﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Acid.Compute.IL;

namespace Acid.Compute
{
    /// <summary>
    /// Resolves Vaiable Names
    /// </summary>
    public class CodeRestorer
    {
        public class Variable
        {
            public string Name { get; set; }
            public object Value { get; set; }
            public Type Type { get; set; }
            public List<Type> Attributes { get; set; }
            public int Index { get; set; }


            public Variable()
            {
                this.Attributes = new List<Type>();
            }
        }

        
        private Stack<string>       AddressStack;
        private Stack<string>       Stack;
        private List<Variable>      Fields;      // Analogous to Uniforms
        private List<Variable>      Arguments;   // Vertex Shader Arguments
        private List<Variable>      Locals;      // Vertex Shader Locals
        
        public CodeRestorer(Type type)
        {
            Globals.LoadOpCodes();
            this.Stack         = new Stack<string>();
            this.AddressStack  = new Stack<string>();
            this.Fields        = new List<Variable>();
            this.Arguments     = new List<Variable>();
            this.Locals        = new List<Variable>();
            

            Type program = type;

            this.LoadFields();

            foreach (MethodInfo methodInfo in program.GetMethods())
            {
                if (methodInfo.Name == "VertexProgram")
                {
                    this.LoadArguments(methodInfo, this.Arguments);
                    
                    this.LoadLocals   (methodInfo,    this.Locals);
                }
            }

            foreach (MethodInfo info in program.GetMethods())
            {
                if (info.Name == "VertexProgram")
                {                   
                    MethodReader reader = new MethodReader(info);

                    for (int i = 0; i < reader.instructions.Count; i++)
                    {
                        ReadInstruction(reader.instructions, ref i);
                         
                        ILInstruction instruction = reader.instructions[i];

                        Console.WriteLine(instruction.Offset.ToString() + " " + instruction.Code.Name + " " + ((instruction.Operand != null) ? instruction.Operand.ToString() : string.Empty));
                    }
                }
            }
            debug();
        }

        #region Tables

        private void LoadFields()
        {
            Type type = this.GetType();
            int index = 0;
            foreach (FieldInfo fieldInfo in type.GetFields())
            {
                if (this.IsCompatiableType(fieldInfo.FieldType))
                {
                    Variable variable = new Variable();
                    variable.Name     = fieldInfo.Name;
                    variable.Type     = fieldInfo.FieldType;
                    variable.Value    = fieldInfo.GetValue(this);
                    variable.Index    = index;
                    foreach (object attribute in fieldInfo.GetCustomAttributes(true))
                        variable.Attributes.Add(attribute.GetType());
                    Fields.Add(variable);
                    index++;
                }
            }
        }

        private void LoadArguments(MethodInfo methodInfo, List<Variable> variables)
        {
            int index = 1;
            foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
            {
                Variable variable = new Variable();
                variable.Name = parameterInfo.Name;
                variable.Type = parameterInfo.ParameterType;
                variable.Index = index;
                foreach (object attribute in variable.Attributes)
                    variable.Attributes.Add(attribute.GetType());
                variable.Value = null;
                variables.Add(variable);
                index++;
            }
        }

        private void LoadLocals(MethodInfo methodInfo, List<Variable> variables)
        {
            MethodBody methodBody = methodInfo.GetMethodBody();
            foreach (LocalVariableInfo localVariableInfo in methodBody.LocalVariables)
            {
                if (this.IsCompatiableType(localVariableInfo.LocalType))
                {
                    Variable variable = new Variable();
                    variable.Name = "r" + localVariableInfo.LocalIndex.ToString();
                    variable.Type = localVariableInfo.LocalType;
                    variable.Value = null;
                    variable.Index = localVariableInfo.LocalIndex;
                    variables.Add(variable);
                }
            }
        }

        #endregion

        #region Utilities

        private bool   IsCompatiableType(Type type)
        {
            if (type == typeof(byte) ||
                type == typeof(int) ||
                type == typeof(float) ||
                type == typeof(double) ||
                type == typeof(Acid.Math.Vector2) ||
                type == typeof(Acid.Math.Vector3) ||
                type == typeof(Acid.Math.Vector4) ||
                type == typeof(Acid.Math.Matrix) ||
                type == typeof(Acid.Graphics.Texture1D) ||
                type == typeof(Acid.Graphics.Texture2D) ||
                type == typeof(Acid.Graphics.Texture3D) ||
                type == typeof(Acid.Graphics.TextureCube))
            {
                return true;
            }
            return false;
        }

        private string ConvertType(Type type)
        {
            if (type == typeof(byte))                      return "byte";
            if (type == typeof(int))                       return "int";
            if (type == typeof(float))                     return "float";
            if (type == typeof(double))                    return "double";
            if (type == typeof(Acid.Math.Vector2))         return "vec2";
            if (type == typeof(Acid.Math.Vector3))         return "vec3";
            if (type == typeof(Acid.Math.Vector4))         return "vec4";
            if (type == typeof(Acid.Math.Matrix))          return "mat4";
            if (type == typeof(Acid.Graphics.Texture1D))   return "sampler1D";
            if (type == typeof(Acid.Graphics.Texture2D))   return "sampler2D";
            if (type == typeof(Acid.Graphics.Texture3D))   return "sampler3D";
            if (type == typeof(Acid.Graphics.TextureCube)) return "samplerCube";
            return "var";
        }

        #endregion

        #region Readers

        public void ReadInitializeObject(List<ILInstruction> instructions, ref int index)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("INITIALIZING NEW OBJECT");
            Console.ForegroundColor = ConsoleColor.White;
        }
        /// <summary>
        /// Call Method
        /// </summary>
        /// <param name="instructions"></param>
        /// <param name="index"></param>
        public void read_call(List<ILInstruction> instructions, ref int index)
        {
            string[] split = instructions[index].Operand.ToString().Split(new char[] { '(' });
            string funcName = split[0].Split(new char[] { ' ' })[1];      // The Function Name
            int    funcArgs = split[1].Split(new char[] { ',' }).Count(); // The Function Argument Count
            string result = ")";
            for (int i = 0; i < funcArgs; i++)
                if (i == funcArgs - 1)
                    result = Stack.Pop() + result;
                else
                    result = ", " + Stack.Pop() + result;
            result = funcName + "(" + result;
            if (Stack.Count == 0)
                Stack.Push(result + ";");
            else
                Stack.Push(result);
        }
        /// <summary>
        /// Load Field Instruction
        /// </summary>
        /// <param name="instructions"></param>
        /// <param name="index"></param>
        public void read_ldfld (List<ILInstruction> instructions, ref int index)
        {
            string scope = string.Empty;
            
            if (Stack.Count > 0)
                scope += Stack.Pop() + ".";
            while (this.AddressStack.Count > 0)
                scope += this.AddressStack.Pop() + ".";
            
            string [] split = instructions[index].Operand.ToString().Split(new char [] {' '});

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("PUSHING FIELD ON STACK ------->" + scope + split[1]);
            Console.ForegroundColor = ConsoleColor.White;
            Stack.Push(scope + split[1]);

             
        }
        /// <summary>
        /// Load Constaint
        /// </summary>
        /// <param name="instructions"></param>
        /// <param name="index"></param>
        public void read_ldc(List<ILInstruction> instructions, ref int index)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("LOADING CONTSTANT");
            Console.ForegroundColor = ConsoleColor.White;

            if (instructions[index].Code.Name == "ldc.i4.0") { Stack.Push("0"); return; }
            if (instructions[index].Code.Name == "ldc.i4.1") { Stack.Push("1"); return; }
            if (instructions[index].Code.Name == "ldc.i4.2") { Stack.Push("2"); return; }
            if (instructions[index].Code.Name == "ldc.i4.3") { Stack.Push("3"); return; }
            if (instructions[index].Code.Name == "ldc.i4.4") { Stack.Push("4"); return; }
            if (instructions[index].Code.Name == "ldc.i4.5") { Stack.Push("5"); return; }
            if (instructions[index].Code.Name == "ldc.i4.6") { Stack.Push("6"); return; }
            if (instructions[index].Code.Name == "ldc.i4.7") { Stack.Push("7"); return; }
            if (instructions[index].Code.Name == "ldc.i4.8") { Stack.Push("8"); return; }
            if (instructions[index].Code.Name == "ldc.i4.s") { Stack.Push(instructions[index].Operand.ToString()); return; }
            if (instructions[index].Code.Name == "ldc.r4") { Stack.Push(instructions[index].Operand.ToString()); return; }
            if (instructions[index].Code.Name == "ldc.i8") { Stack.Push(instructions[index].Operand.ToString()); return; }

        }
        /// <summary>
        /// Load Field Address Instruction
        /// </summary>
        /// <param name="instructions"></param>
        /// <param name="index"></param>
        public void read_ldflda(List<ILInstruction> instructions, ref int index)
        {
            string[] split = instructions[index].Operand.ToString().Split(new char[] { ' ' });

            this.AddressStack.Push(split[1]);

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("LOADING FIELD SCOPE ----------------->" + split[1]);
            Console.ForegroundColor = ConsoleColor.White;
        }
        /// <summary>
        /// Loads the Arguments on the Stack, Excludes the ldarg.0
        /// </summary>
        /// <param name="instructions"></param>
        /// <param name="index"></param>
        public void read_ldarg (List<ILInstruction> instructions, ref int index)
        {
            if (instructions[index].Code.Name == "ldarg.s")
            {
                int idx = 0;

                if (int.TryParse(instructions[index].Operand.ToString(), out idx))
                {
                    Variable variable = this.Arguments.Where(n => n.Index == idx).FirstOrDefault();

                    if (variable != null)
                    {
                        Stack.Push(variable.Name);

                        Console.ForegroundColor = ConsoleColor.Green;
                        Console.WriteLine("PUSHING ARGUMENT ON STACK ---->" + variable.Name);
                        Console.ForegroundColor = ConsoleColor.White;
                    }
                }
            }
            else
            {
                string[] split = instructions[index].Code.Name.Split(new char[] { '.' });

                int idx = 0;

                if (int.TryParse(split[1], out idx))
                {
                    if (idx > 0) // ignore ldarg.0 (this)
                    {
                        Variable variable = this.Arguments.Where(n => n.Index == idx).FirstOrDefault();

                        if (variable != null)
                        {
                            Stack.Push(variable.Name);

                            Console.ForegroundColor = ConsoleColor.Green;
                            Console.WriteLine("PUSHING ARGUMENT ON STACK ---->" + variable.Name);
                            Console.ForegroundColor = ConsoleColor.White;
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Load Argument Address 
        /// </summary>
        /// <param name="instructions"></param>
        /// <param name="index"></param>
        public void read_ldarga(List<ILInstruction> instructions, ref int index)
        {
            int idx = 0;

            if (int.TryParse(instructions[index].Operand.ToString(), out idx))
            {
                Variable variable = this.Arguments.Where(n => n.Index == idx).FirstOrDefault();

                if (variable != null)
                {
                    Stack.Push(variable.Name);
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine("PUSHING ARGUMENT ADDRESS ON STACK ---->" + variable.Name);
                    Console.ForegroundColor = ConsoleColor.White;
                }
            }
        }
        /// <summary>
        /// Load Local
        /// </summary>
        /// <param name="instructions"></param>
        /// <param name="index"></param>
        public void read_ldloc (List<ILInstruction> instructions, ref int index)
        {
            if (instructions[index].Code.Name == "ldloc.s")
            {
                int idx = 0;

                if (int.TryParse(instructions[index].Operand.ToString(), out idx))
                {
                    Variable variable = this.Locals.Where(n => n.Index == idx).FirstOrDefault();

                    if (variable != null)
                    {
                        Stack.Push(variable.Name);
                        Console.ForegroundColor = ConsoleColor.Green;
                        Console.WriteLine("PUSHING LOCAL ON STACK ---->" + variable.Name);
                        Console.ForegroundColor = ConsoleColor.White;
                    }
                }
            }
            else
            {
                string[] split = instructions[index].Code.Name.Split(new char[] { '.' });

                int idx = 0;

                if (int.TryParse(split[1], out idx))
                {
                    Variable variable = this.Locals.Where(n => n.Index == idx).FirstOrDefault();

                    if (variable != null)
                    {
                        Stack.Push(variable.Name);
                        Console.ForegroundColor = ConsoleColor.Green;
                        Console.WriteLine("PUSHING LOCAL ON STACK ---->" + variable.Name);
                        Console.ForegroundColor = ConsoleColor.White;
                    }
                    
                }
            }

        }
        /// <summary>
        /// Store Local
        /// </summary>
        /// <param name="instruction"></param>
        /// <param name="index"></param>
        public void ld_stloc(List<ILInstruction> instructions, ref int index)
        {
            string scope = string.Empty;

            if (instructions[index].Code.Name.Contains("stloc.s"))
            {
                 int idx = 0;

                 if (int.TryParse(instructions[index].Operand.ToString(), out idx))
                 {
                     Variable variable = this.Locals.Where(n => n.Index == idx).FirstOrDefault();

                     if (variable != null)
                     {
                         Console.ForegroundColor = ConsoleColor.Green;
                         Console.WriteLine("STORING LOCAL ---->" + variable.Name + " = " + Stack.Pop());
                         Console.ForegroundColor = ConsoleColor.White;
                     }
                 }
            }
            else
            {
                string[] split = instructions[index].Code.Name.Split(new char[] { '.' });

                int idx = 0;

                if (int.TryParse(split[1], out idx))
                {
                    Variable variable = this.Locals.Where(n => n.Index == idx).FirstOrDefault();

                    if (variable != null)
                    {
                  
                            Console.ForegroundColor = ConsoleColor.Green;
                            Console.WriteLine("STORING LOCAL ---->" + variable.Name + " = " + Stack.Pop());
                            Console.ForegroundColor = ConsoleColor.White;
                        
                    }
                     
                }
            }
        }
        /// <summary>
        /// Store Field
        /// </summary>
        /// <param name="instruction"></param>
        /// <param name="index"></param>
        public void read_stfld(List<ILInstruction> instructions, ref int index)
        {
            string scope = string.Empty;
            if (Stack.Count > 0) 
                scope += Stack.Pop() + ".";
            while (this.AddressStack.Count > 0)
                 scope += this.AddressStack.Pop() + ".";

            string[] split = instructions[index].Operand.ToString().Split(new char[] { ' ' });

            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("STORING FIELD ---------------->" + scope + split[1]);
            Console.ForegroundColor = ConsoleColor.White;
        }
        /// <summary>
        /// Return from Method
        /// </summary>
        /// <param name="instruction"></param>
        /// <param name="index"></param>
        public void read_ret    (List<ILInstruction> instruction, ref int index)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("METHOD RETURNING");
            Console.ForegroundColor = ConsoleColor.White;
            
            if(Stack.Count > 0) // method has a return type.
                Console.WriteLine(Stack.Pop());
        }
        

        public void ReadInstruction(List<ILInstruction> instructions, ref int index)
        {
            if (instructions[index].Code.ToString().Contains("ldarga"))
            {
                read_ldarga(instructions, ref index); return;
            }
            if (instructions[index].Code.ToString().Contains("ldarg"))
            {
                read_ldarg(instructions, ref index); return;
            }
            if (instructions[index].Code.ToString().Contains("ldflda"))
            {
                read_ldflda(instructions, ref index); return;
            }
            if (instructions[index].Code.ToString().Contains("ldfld"))
            {
                read_ldfld(instructions, ref index); return;
            }
            if (instructions[index].Code.ToString().Contains("ldloc"))
            {
                read_ldloc(instructions, ref index); return;
            }
            if (instructions[index].Code.ToString().Contains("stloc"))
            {
                ld_stloc(instructions, ref index); return;
            }
            if (instructions[index].Code.ToString().Contains("stfld"))
            {
                read_stfld(instructions, ref index); return;
            }
            if (instructions[index].Code.ToString().Contains("call"))
            {
                read_call(instructions, ref index); return;
            }
            if (instructions[index].Code.ToString().Contains("ret"))
            {
                read_ret(instructions, ref index); return;
            }
            if (instructions[index].Code.ToString().Contains("initobj"))
            {
                ReadInitializeObject(instructions, ref index); return;
            }
            if (instructions[index].Code.ToString().Contains("ldc"))
            {
                read_ldc(instructions, ref index); return;
            }
             
        }

        #endregion
        public void debug()
        {
            foreach (Variable v in Fields)
            {
                Console.Write("        ");

                Console.WriteLine(v.Index.ToString() + " " + ConvertType(v.Type) + " " + v.Name + " ");
            }

            Console.WriteLine("Arguments");

            foreach (Variable v in Arguments)
            {
                Console.Write("        ");

                Console.WriteLine(v.Index.ToString() + " " + ConvertType(v.Type) + " " + v.Name + " ");
            }

            Console.WriteLine("Locals");

            foreach (Variable v in Locals)
            {
                Console.Write("        ");

                Console.WriteLine(v.Index.ToString() + " " + ConvertType(v.Type) + " " + v.Name + " ");
            }
        }
        
    }
}
